﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using rkShared.Net;
using System.Windows.Forms;
using System.IO;

namespace rkServer
{
    public class GameServer : IDisposable
    {
        private const int RECEIVE_BUFFER_SIZE = 8192;

        private object m_SyncObject;

        private Socket m_udpServer;
        private Socket m_tcpServer;
        /// <summary>
        /// Holds a dictionary containing <ClientID, Socket> pairs for each connected client.
        /// </summary>
        private Dictionary<ClientInfo, Socket> m_tcpClients;
        private List<ClientInfo> m_clients;
        internal Guid m_serverGuid;
        /// <summary>
        /// We hold this list, because we'll close all open sockets and destroy MessageReaders to prevent orphened threads when application has exited.
        /// </summary>
        private List<MessageReader> m_readers;

        public delegate void ClientDisconnectedEventHandler(ClientInfo client, DisconnectReason reason);
        public event MessageReceivedEventHandler ChatMessageReceived;
        public event MessageReceivedEventHandler GameMessageReceived;
        public event ClientRegisteredEventHandler ClientRegistered;
        public event ClientDisconnectedEventHandler ClientDisconnected;
        public event ClientEnteredGameEventHandler ClientEnteredGame;

        public GameServer(int port)
        {
            m_readers = new List<MessageReader>();
            m_SyncObject = new object();
            m_serverGuid = Guid.NewGuid();
            m_tcpClients = new Dictionary<ClientInfo, Socket>();
            m_clients = new List<ClientInfo>();
            this.InitUDPSocket(port);
            this.InitTCPSocket(port);
        }

        private Socket GetClientUDPSocket()
        {
            Socket clientSocket = new Socket(AddressFamily.InterNetwork,
                                    SocketType.Dgram,
                                    ProtocolType.Udp);

            return clientSocket;

        }
        /// <summary>
        /// Creates the udp socket and starts the message reader to begin listening incoming client messages.
        /// </summary>
        /// <param name="port"></param>
        private void InitUDPSocket(int port)
        {
            m_udpServer = this.GetClientUDPSocket();
            m_udpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
            m_udpServer.Bind(new IPEndPoint(IPAddress.Any, port));

            MessageReader _reader = new MessageReader(m_udpServer);
            m_readers.Add(_reader);
            _reader.MessageReceived += new MessageReader.MessageReceivedEventHandler(this.NetMessageReceived);
            _reader.SocketErrorOccured += new EventHandler(UdpSocketErrorOccured);
            _reader.Start();
        }

        void UdpSocketErrorOccured(object sender, EventArgs e)
        {
            //udp error occured...
        }
        /// <summary>
        /// creates tcp socket and begins accepting the clients...
        /// </summary>
        /// <param name="port"></param>
        private void InitTCPSocket(int port)
        {
            m_tcpServer = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
            m_tcpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
            m_tcpServer.Bind(new IPEndPoint(IPAddress.Any, port));
            m_tcpServer.Listen(10);

            m_tcpServer.BeginAccept(this.OnTcpClientAccepted, m_tcpServer);
        }
        /// <summary>
        /// Called when tcp socket accepts new tcp connection. Accepts the client and creates 
        /// a <see cref="rkShared.Net.MessageReader"/> instance to listen the messages from that client. 
        /// </summary>
        /// <param name="ar"></param>
        private void OnTcpClientAccepted(IAsyncResult ar)
        {
            Socket _client = null;
            try
            {
                _client = m_tcpServer.EndAccept(ar);
            }
            catch
            {
                return;
            }
            MessageReader _reader = new MessageReader(_client);
            m_readers.Add(_reader);
            _reader.MessageReceived += new MessageReader.MessageReceivedEventHandler(NetMessageReceived);
            _reader.SocketErrorOccured += new EventHandler(this.SocketErrorOccured);
            _reader.Start();

            m_tcpServer.BeginAccept(this.OnTcpClientAccepted, ar.AsyncState);
        }
        private void SocketErrorOccured(object sender, EventArgs e)
        {
            foreach (KeyValuePair<ClientInfo, Socket> pair in m_tcpClients)
            {
                if (pair.Value.Handle.ToInt32() == (sender as MessageReader).m_socket.Handle.ToInt32())
                {
                    this.OnClientDisconnected(pair.Key, DisconnectReason.SocketException);
                    break;
                }
            }
            this.StopMessageReader(sender as MessageReader);
        }
        /// <summary>
        /// Called by the MessageReader whenever a NetMessage is received over tcp...
        /// </summary>
        /// <param name="msg"></param>
        private void NetMessageReceived(MessageReader reader, NetMessage msg)
        {
            switch (msg.MessageType)
            {
                case MessageType.Register:
                    //add client and socket to m_tcpClients...
                    this.OnClientRegistered(msg, reader.m_socket);
                    break;
                case MessageType.ClientEnteredGame:
                    this.OnClientEnteredGame(msg);
                    break;
                case MessageType.Chat:
                    this.OnChatMessageReceived(msg);
                    break;
                case MessageType.Disconnect:
                    this.OnClientDisconnected(this.FindClient(msg.ClientID), DisconnectReason.DisconnectMessage);
                    this.StopMessageReader(reader);
                    break;
                default:
                    this.OnGameMessageReceived(msg);
                    break;
            }
        }

        private void StopMessageReader(MessageReader reader)
        {
            reader.Stop();
            m_readers.Remove(reader);
        }
        public ClientInfo FindClient(Guid clientID)
        {
            foreach (ClientInfo c in m_clients)
            {
                if (c.ClientID == clientID)
                    return c;
            }
            return null;
        }
        private void OnClientEnteredGame(NetMessage msg)
        {
            ClientInfo c = this.FindClient(msg.ClientID);
            if (c != null)
            {
                if (this.ClientEnteredGame != null)
                    this.ClientEnteredGame(c, msg);
            }

        }
        private void OnChatMessageReceived(NetMessage msg)
        {
            ClientInfo c = this.FindClient(msg.ClientID);
            if (c != null)
            {
                if (this.ChatMessageReceived != null)
                    this.ChatMessageReceived(c, msg);
            }
        }
        private void OnGameMessageReceived(NetMessage msg)
        {
            if (this.GameMessageReceived != null)
            {
                ClientInfo _client = this.FindClient(msg.ClientID);
                if (_client != null)
                    this.GameMessageReceived(_client, msg);
            }
        }

        protected virtual void OnClientRegistered(NetMessage msg, Socket socket)
        {
            m_tcpClients[msg.Data as ClientInfo] = socket;
            m_clients.Add(msg.Data as ClientInfo);

            if (this.ClientRegistered != null)
                foreach (Delegate d in this.ClientRegistered.GetInvocationList())
                    d.DynamicInvoke(msg.Data as ClientInfo);
        }
        protected virtual void OnClientDisconnected(ClientInfo client, DisconnectReason reason)
        {
            //Remove the client from inner list and fire the ClientDisconnected event...

            lock (this.SyncObject)
            {
                m_clients.Remove(client);
                m_tcpClients.Remove(client);


                if (this.ClientDisconnected != null)
                    this.ClientDisconnected.BeginInvoke(client, reason, this.EventFiredCallback, this.ClientDisconnected);


            }
        }
        protected void EventFiredCallback(IAsyncResult ar)
        {
        }
        /// <summary>
        /// Sends a message to the client over TCP.
        /// </summary>
        /// <param name="to">The client to send the message to.</param>
        /// <param name="msg">The message to send.</param>
        public void SendTcpMessage(ClientInfo to, NetMessage msg)
        {
            this.SendMessage(to, msg, ProtocolType.Tcp);
        }
        /// <summary>
        /// Sends a message to the client over UDP.
        /// </summary>
        /// <param name="to">The client to send the message to.</param>
        /// <param name="msg">The message to send.</param>
        public void SendUdpMessage(ClientInfo to, NetMessage msg)
        {
            this.SendMessage(to, msg, ProtocolType.Udp);
        }
        public void SendMessage(ClientInfo client, NetMessage msg, ProtocolType protocol)
        {
            if (msg.ClientID == Guid.Empty)
                msg.ClientID = m_serverGuid;
            if (protocol == ProtocolType.Tcp)
            {
                ThreadPool.QueueUserWorkItem(this.SendTcpMessage,
                 new object[] { client, msg });
            }
            else if (protocol == ProtocolType.Udp)
            {
                ThreadPool.QueueUserWorkItem(this.SendUdpMessage,
                    new object[] { client, msg });
            }
        }
        private void SendUdpMessage(object state)
        {
            object[] _state = state as object[];
            ClientInfo client = _state[0] as ClientInfo;
            NetMessage msg = _state[1] as NetMessage;

            try
            {
                Socket _socket = this.GetClientUDPSocket();
                msg.SendOverUDP(_socket, client.IPAddress, client.Port);
            }
            catch (SocketException ex)
            {
                if (m_clients.Contains(client))
                    m_clients.Remove(client);
                if (this.ClientDisconnected != null)
                    this.ClientDisconnected(client, DisconnectReason.SocketException);

            }
        }
        private void SendTcpMessage(object state)
        {
            object[] _state = state as object[];
            ClientInfo client = _state[0] as ClientInfo;
            NetMessage msg = _state[1] as NetMessage;

            if (m_tcpClients.ContainsKey(client))
            {
                try
                {
                    Socket _socket = m_tcpClients[client];
                    msg.SendOverTCP(_socket);
                }
                catch (SocketException ex)
                {
                    if (m_clients.Contains(client))
                        m_clients.Remove(client);
                    if (this.ClientDisconnected != null)
                        this.ClientDisconnected(client, DisconnectReason.SocketException);

                }
            }
        }
        public List<ClientInfo> Clients
        {
            get
            {
                return m_clients;
            }
        }
        public Object SyncObject
        {
            get
            {
                return m_SyncObject;
            }
        }

        #region IDisposable Members
        /// <summary>
        /// Disposes this instance and closes the sockets after waiting 5 seconds...
        /// </summary>
        public void Dispose()
        {
            this.Dispose(5);
        }
        /// <summary>
        /// Disposes all resource held by this instance, closes the sockets after waiting for a specefied timeout seconds 
        /// to let them send any remaining data.
        /// </summary>
        /// <param name="timeout"></param>
        public void Dispose(int timeout)
        {
            try
            {
                foreach (MessageReader reader in m_readers)
                    reader.Stop();
                m_udpServer.Close(timeout);
                m_tcpServer.Close(timeout);
                m_readers.Clear();
            }
            catch { }
        }
        #endregion
    }
}
